package com.nts.visionworks.business.application.service;

import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.nts.visionworks.business.application.domain.ApplicationMenu;
import com.nts.visionworks.business.application.domain.ApplicationMenuContents;
import com.nts.visionworks.business.application.domain.ApplicationSetting;
import com.nts.visionworks.business.application.domain.ServiceApplication;
import com.nts.visionworks.business.application.domain.ServiceChannel;
import com.nts.visionworks.business.contents.domain.Contents;
import com.nts.visionworks.business.endpoint.domain.EndPoint;
import com.nts.visionworks.core.hibernate.transform.AutoCastableResultTransformer;

/**
 * 서비스 애플리케이션에 대한 DAO를 제공한다.
 * @author Devcken
 */
@Service("applicationService")
@Transactional
public class ApplicationService
{
	/**
	 * VisionWorks {@link org.hibernate.SessionFactory}를 주입받는다.
	 */
	@Resource(name="visionworksSessionFactory")
	private SessionFactory _sessionFactory;
	
	/**
	 * 서비스 애플리케이션 목록을 조회한다.
	 * @return 조회된 서비스 애플리케이션 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ServiceApplication> listServiceApplications()
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(ServiceApplication.class);
			
			List<ServiceApplication> applications = (List<ServiceApplication>)criteria.list();
			
			return applications;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 서비스 애플리케이션 정보를 저장한다.
	 * @param application 저장하려는 서비스 애플리케이션 정보를 설정한다.
	 * @return 주어진 서비스 애플리케이션 정보가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveServiceApplication(ServiceApplication serviceApplication)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			if (serviceApplication.getApplicationId() != null &&
					serviceApplication.getApplicationId() > 0)
			{
				String applicationName = serviceApplication.getApplicationName();
				
				Criteria serviceApplicationCriteria = session.createCriteria(ServiceApplication.class)
						.add(Restrictions.eq("applicationId", serviceApplication.getApplicationId()));
				
				serviceApplication = (ServiceApplication)serviceApplicationCriteria.uniqueResult();
				
				// 서비스 애플리케이션이 사용 불가능한 상태면 수정하지 못한다.
				if (!serviceApplication.getUsable())
				{
					return false;
				}
				
				serviceApplication.setApplicationName(applicationName);
				serviceApplication.setUpdatedDateTime(new Date());
			}
			else
			{
				serviceApplication.setCreatedDateTime(new Date());
				
				// 새로 등록되는 애플리케이션의 사용 여부는 항상 가능으로 설정한다. 
				serviceApplication.setUsable(true);
			}
			
			session.saveOrUpdate(serviceApplication);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 서비스 애플리케이션을 사용 불가능한 상태로 변경한다.
	 * @param applicationId 사용 불가능한 상태로 변경할 서비스 애플리케이션의 아이디를 설정한다.
	 * @return 주어진 애플리케이션 아이디에 대한 서비스 애플리케이션을 사용 불가능한 상태로 변경한 경우 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean diableServiceApplication(int applicationId)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria applicationCriteria = session.createCriteria(ServiceApplication.class)
					.add(Restrictions.eq("applicationId", applicationId));
			
			ServiceApplication serviceApplication = (ServiceApplication)applicationCriteria.uniqueResult();
			
			serviceApplication.setUsable(false);
			
			session.save(serviceApplication);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 주어진 서비스 애플리케이션의 환경 설정 목록을 조회한다.
	 * @param applicationId 환경 설정 목록을 조회하려는 서비스 애플리케이션의 아이디를 설정한다.
	 * @return 주어진 서비스 애플리케이션의 환경 설정 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ApplicationSetting> listServiceApplicationSettings(int applicationId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(ApplicationSetting.class)
					.add(Restrictions.eq("id.applicationId", applicationId));
			
			List<ApplicationSetting> applicationSettings = (List<ApplicationSetting>)criteria.list();
			
			return applicationSettings;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 서비스 애플리케이션 환경 설정 정보를 저장한다.
	 * @param applicationSetting 저장하려는 서비스 애플리케이션 환경 설정 정보를 설정한다.
	 * @return 서비스 애플리케이션 환경 설정 정보가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveServiceApplicationSetting(ApplicationSetting applicationSetting)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			session.saveOrUpdate(applicationSetting);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 서비스 애플리케이션 환경 설정 정보를 삭제한다.
	 * @param settingId 삭제하려는 환경 설정 아이디를 설정한다.
	 * @param applicationId 삭제하려는 환경 설정에 대한 애플리케이션 아이디를 설정한다.
	 * @return 서비스 애플리케이션 환경 설정 정보가 삭제되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean deleteServiceApplicationSetting(int settingId, int applicationId)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria applicationSettingCriteria = session.createCriteria(ApplicationSetting.class)
					.add(Restrictions.eq("id.settingId", settingId))
					.add(Restrictions.eq("id.applicationId", applicationId));
			
			ApplicationSetting applicationSetting = (ApplicationSetting)applicationSettingCriteria.uniqueResult();
			
			session.delete(applicationSetting);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 주어진 서비스 애플리케이션에 속하는 끝점 목록을 조회한다.
	 * @param applicationId 서비스 애플리케이션 아이디를 설정한다.
	 * @return 주어진 서비스 애플리케이션에 속하는 끝점 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<EndPoint> listServiceApplicationEndPoints(int applicationId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(EndPoint.class)
					.add(Restrictions.eq("applicationId", applicationId));
			
			List<EndPoint> endPoints = (List<EndPoint>)criteria.list();
			
			return endPoints;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 서비스 애플리케이션에 속하는 채널 목록을 조회한다.
	 * @param applicationId 서비스 애플리케이션 아이디를 설정한다.
	 * @return 주어진 서비스 애플리케이션 아이디에 대한 채널 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ServiceChannel> listServiceChannels(int applicationId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(ServiceChannel.class)
					.add(Restrictions.eq("serviceApplication.applicationId", applicationId));
			
			List<ServiceChannel> serviceChannels = (List<ServiceChannel>)criteria.list();
			
			return serviceChannels;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 서비스 채널을 저장한다.
	 * @param serviceChannel 저장하려는 서비스 채널 정보를 설정한다.
	 * @param applicationId 서비스 채널이 소속되는 서비스 애플리케이션의 아이디를 설정한다.
	 * @return 서비스 채널 정보가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveServiceChannel(ServiceChannel serviceChannel, int applicationId)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria serviceApplicationCriteria = session.createCriteria(ServiceApplication.class)
					.add(Restrictions.eq("applicationId", applicationId));
			
			ServiceApplication serviceApplication = (ServiceApplication)serviceApplicationCriteria.uniqueResult();
			
			serviceChannel.setServiceApplication(serviceApplication);
			
			session.save(serviceChannel);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 애플리케이션의 메뉴를 조회한다.
	 * @param applicationId 조회할 애플리케이션 메뉴의 애플리케이션 아이디를 설정한다.
	 * @param startLikeHierachicalLabel 계층 라벨에 대해 검색하기 위해 검색 파라메터를 설정한다.
	 * @return 조회된 애플리케이션 메뉴 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ApplicationMenu> listApplicationMenus(int applicationId, String startLikeHierachicalLabel)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Query query = session.getNamedQuery("Application.ListApplicationMenuHierachically")
					.setString("StartLikeHierachicalLabel", startLikeHierachicalLabel)
					.setInteger("ApplicationId", applicationId)
					.setResultTransformer(new AutoCastableResultTransformer<ApplicationMenu>(ApplicationMenu.class));
			
			return query.list();
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 애플리케이션 메뉴를 조회한다.
	 * @param applicationId 조회할 애플리케이션 메뉴의 애플리케이션 아이디를 설정한다.
	 * @return 조회된 애플리케이션 메뉴 목록이 반환된다.
	 */
	public List<ApplicationMenu> listApplicationMenus(int applicationId)
	{
		return this.listApplicationMenus(applicationId, StringUtils.EMPTY);
	}
	
	/**
	 * 애플리케이션 메뉴를 저장한다.
	 * @param applicationId 애플리케이션 메뉴의 애플리케이션 아이디를 설정한다.
	 * @param applicationMenu 저장하려는 애플리케이션 메뉴 정보를 설정한다.
	 * @return 애플리케이션 메뉴가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveApplicationMenu(int applicationId, ApplicationMenu applicationMenu)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria serviceApplicationCriteria = session.createCriteria(ServiceApplication.class)
					.add(Restrictions.eq("applicationId", applicationId));
			
			ServiceApplication serviceApplication = (ServiceApplication)serviceApplicationCriteria.uniqueResult();
			
			applicationMenu.setServiceApplication(serviceApplication);
			
			applicationMenu.setUsable(true);
			applicationMenu.setCreatedDateTime(new Date());
			
			session.save(applicationMenu);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 애플리케이션 메뉴의 사용 여부 상태를 변경한다. 
	 * @param menuId 사용 여부 상태를 변경할 애플리케이션 메뉴의 아이디를 설정한다.
	 * @param usable 변경할 사용 여부를 설정한다.
	 * @return 애플리케이션 메뉴의 사용 여부 상태가 변경되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean updateApplicationMenuUsable(long menuId, boolean usable)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria applicationMenuCriteria = session.createCriteria(ApplicationMenu.class)
					.add(Restrictions.eq("menuId", menuId));
			
			ApplicationMenu applicationMenu = (ApplicationMenu)applicationMenuCriteria.uniqueResult();
			
			// 변경하려는 상태와 현재의 상태가 동일할 경우 성공 처리한다.
			if (applicationMenu.isUsable() == usable) return true;
			
			applicationMenu.setUsable(usable);
			
			session.save(applicationMenu);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 애플리케이션 메뉴를 삭제한다.
	 * @param menuId 삭제하려는 애플리케이션 메뉴 아이디를 설정한다.
	 * @return 애플리케이션 메뉴가 삭제되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean deleteApplicationMenu(long menuId)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Query query = session.getNamedQuery("Application.DeleteApplicationMenu")
					.setLong("MenuId", menuId);
			
			query.executeUpdate();
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 애플리케이션 메뉴의 컨텐츠 목록을 조회한다.
	 * @param menuId 컨텐츠 목록을 조회하려는 애플리케이션 메뉴의 아이디를 설정한다.
	 * @return 애플리케이션 메뉴의 컨텐츠 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<Contents> listApplicationMenuContents(long menuId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(ApplicationMenuContents.class, "mc")
					.createAlias("contents", "co")
					.add(Restrictions.eq("mc.menuId", menuId));
			
			List<Contents> contents = (List<Contents>)criteria.list();
			
			return contents;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
}